package com.csap.framework.oauth2.client.boot;

import cn.dev33.satoken.LoginUtil;
import cn.dev33.satoken.config.SaTokenConfig;
import com.csap.framework.boot.apidoc.autoconfigure.EnableApidoc;
import com.csap.framework.oauth2.FindUserService;
import com.csap.framework.oauth2.UserInfo;
import com.csap.framework.oauth2.UserInfoEndpoint;
import com.csap.framework.oauth2.client.Oauth2ClientEndpoint;
import com.csap.framework.oauth2.client.Oauth2ClientProperties;
import com.csap.framework.oauth2.client.feign.ClientRequestInterceptor;
import com.csap.framework.oauth2.client.rest.Oauth2ClientApi;
import com.csap.framework.oauth2.client.rest.Oauth2ClientApiLocal;
import com.csap.framework.oauth2.client.rest.Oauth2ClientApiRest;
import com.csap.framework.oauth2.client.user.DefaultUserInfoServiceImpl;
import com.csap.framework.oauth2.client.user.Oauth2UserInfoService;
import com.csap.framework.oauth2.UserInfoStrategyContext;
import com.csap.framework.oauth2.client.user.cache.UserInfoCache;
import com.csap.framework.oauth2.client.user.strategy.FeignFindUserServiceImpl;
import com.csap.framework.oauth2.client.user.strategy.HttpFindUserServiceImpl;
import com.csap.framework.oauth2.client.user.strategy.LocalFindUserServiceImpl;
import com.csap.framework.oauth2.client.user.strategy.RedisFindUserServiceImpl;
import com.csap.framework.oauth2.open.RoleOpenApi;
import com.csap.framework.oauth2.server.Oauth2ServerEndpointApi;
import com.csap.framework.redis.RedisUtil;
import com.csap.framework.util.CollectionsUtil;
import com.csap.framework.util.map.CsapMap;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.common.collect.Lists;
import feign.Client;
import feign.Contract;
import feign.Feign;
import feign.RequestInterceptor;
import feign.Target;
import feign.codec.Decoder;
import feign.codec.Encoder;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.cloud.openfeign.FeignClientsConfiguration;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;

import java.util.List;
import java.util.Map;

/**
 * @author yangchengfu
 * @dataTime 2021年-07月-25日 22:21:00
 **/
@EnableConfigurationProperties(Oauth2ClientProperties.class)
@ConditionalOnProperty(prefix = Oauth2ClientProperties.PREFIX, name = "enabled", havingValue = "true")
@Import(FeignClientsConfiguration.class)
@Configuration
@Slf4j
public class Oauth2ClientAutoConfiguration {


    @ConditionalOnProperty(prefix = Oauth2ClientProperties.PREFIX, name = "client-mod", havingValue = "HTTP")
    @Bean
    public Oauth2ClientApi oauth2ClientApiRest(Oauth2ClientProperties oauth2ClientProperties, Oauth2UserInfoService<? extends UserInfo> oauth2UserInfoService, SaTokenConfig saTokenConfig) {
        return new Oauth2ClientApiRest(oauth2ClientProperties, oauth2UserInfoService, saTokenConfig);
    }

    @ConditionalOnProperty(prefix = Oauth2ClientProperties.PREFIX, name = "client-mod", havingValue = "LOCAL")
    @Bean
    public Oauth2ClientApi oauth2ClientApiLocal(Oauth2ServerEndpointApi serverEndpointApi, UserInfoEndpoint userInfoEndpoint, Oauth2ClientProperties oauth2ClientProperties, SaTokenConfig saTokenConfig, RoleOpenApi roleOpenApi) {
        return new Oauth2ClientApiLocal(serverEndpointApi, userInfoEndpoint, oauth2ClientProperties, saTokenConfig, roleOpenApi);
    }

    @Bean
    @ConditionalOnProperty(prefix = Oauth2ClientProperties.PREFIX, name = "user-info-mod", havingValue = "HTTP")
    public FindUserService httpFindUserServiceImpl(Oauth2ClientProperties oauth2ClientProperties, SaTokenConfig saTokenConfig) {
        return new HttpFindUserServiceImpl(oauth2ClientProperties, saTokenConfig);
    }

    @ConditionalOnProperty(prefix = Oauth2ClientProperties.PREFIX, name = "client-mod", havingValue = "FEIGN")
    @Bean
    public Oauth2ClientApi oauth2ClientApiFeign(Oauth2ClientProperties oauth2ClientProperties, SaTokenConfig saTokenConfig, Client client, Contract contract, Encoder encoder, Decoder decoder, ObjectProvider<ObjectMapper> objectMapper, ApplicationContext applicationContext) {
        List<RequestInterceptor> list = Lists.newArrayList(new ClientRequestInterceptor(oauth2ClientProperties, saTokenConfig, objectMapper.getIfAvailable(ObjectMapper::new)));
        Map<String, RequestInterceptor> beansOfType = applicationContext.getBeansOfType(RequestInterceptor.class);
        if (CollectionsUtil.isNotEmpty(beansOfType)) {
            list.addAll(beansOfType.values());
        }
        log.info("oauth2ClientApiFeign clientMod=feign,{}", list);
        String url = String.format("http://%s/oauth2/", oauth2ClientProperties.getServerName());
        return Feign.builder()
                .requestInterceptors(list)
                .contract(contract)
                .client(client)
                .encoder(encoder)
                .decoder(decoder)
                .target(new Target.HardCodedTarget<>(Oauth2ClientApi.class, oauth2ClientProperties.getServerName(), url));
    }

    @Bean
    public UserInfoStrategyContext userInfoStrategyContext() {
        return new UserInfoStrategyContext();
    }

    @Bean
    public UserInfoCache userInfoCache(Oauth2ClientProperties clientProperties) {
        return new UserInfoCache(clientProperties);
    }

    @Bean
    @ConditionalOnBean(UserInfoEndpoint.class)
    @ConditionalOnProperty(prefix = Oauth2ClientProperties.PREFIX, name = "user-info-mod", havingValue = "LOCAL")
    public FindUserService localFindUserServiceImpl(UserInfoEndpoint userInfoEndpoint) {
        return new LocalFindUserServiceImpl(userInfoEndpoint);
    }

    @Bean
    @ConditionalOnBean(Oauth2ClientApi.class)
    @ConditionalOnProperty(prefix = Oauth2ClientProperties.PREFIX, name = "user-info-mod", havingValue = "FEIGN")
    public FindUserService localFindUserServiceImpl(Oauth2ClientApi clientApi, Oauth2ClientProperties oauth2ClientProperties) {
        return new FeignFindUserServiceImpl(clientApi, oauth2ClientProperties);
    }

    @Bean
    @ConditionalOnMissingBean(Oauth2UserInfoService.class)
    public Oauth2UserInfoService oauth2UserInfoService() {
        return new DefaultUserInfoServiceImpl();
    }


    @Bean
    @ConditionalOnBean(RedisUtil.class)
    @ConditionalOnProperty(prefix = Oauth2ClientProperties.PREFIX, name = "user-info-mod", havingValue = "REDIS")
    public FindUserService redisFindUserServiceImpl(RedisUtil redisUtil, ApplicationContext applicationContext, Oauth2ClientProperties oauth2ClientProperties, SaTokenConfig saTokenConfig) {
        return new RedisFindUserServiceImpl(redisUtil, applicationContext, saTokenConfig, oauth2ClientProperties);
    }

    @SuppressWarnings("SpringJavaInjectionPointsAutowiringInspection")
    @Autowired
    public void loginType(SaTokenConfig saTokenConfig) {
        LoginUtil.init(CollectionsUtil.isEmpty(saTokenConfig.getLoginTypeMap()) ? CsapMap.<String, SaTokenConfig>build().add(saTokenConfig.getLoginType(), saTokenConfig) : saTokenConfig.getLoginTypeMap(), saTokenConfig);

    }

    @SuppressWarnings("SpringJavaInjectionPointsAutowiringInspection")
    @ConditionalOnProperty(prefix = Oauth2ClientProperties.PREFIX, name = "endpoint", havingValue = "true")
    @Configuration
    @EnableApidoc("com.csap.framework.oauth2.client")
    public static class ClientAutoConfiguration {
        @Bean
        public Oauth2ClientEndpoint oauth2ClientEndpoint(Oauth2ClientApi oauth2ClientApi) {
            return new Oauth2ClientEndpoint(oauth2ClientApi);
        }
    }
}
